/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.privacysandbox.ads.adservices.customaudience

import android.net.Uri
import androidx.annotation.IntDef
import androidx.annotation.RestrictTo
import androidx.privacysandbox.ads.adservices.common.AdData
import androidx.privacysandbox.ads.adservices.common.AdSelectionSignals
import androidx.privacysandbox.ads.adservices.common.AdTechIdentifier
import androidx.privacysandbox.ads.adservices.common.ComponentAdData
import androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures
import java.time.Instant

/**
 * Represents the information necessary for a custom audience to participate in ad selection.
 *
 * A custom audience is an abstract grouping of users with similar demonstrated interests. This
 * class is a collection of some data stored on a device that is necessary to serve advertisements
 * targeting a single custom audience.
 *
 * @param buyer A buyer is identified by a domain in the form "buyerexample.com".
 * @param name The custom audience's name is an arbitrary string provided by the owner and buyer on
 *   creation of the [CustomAudience] object.
 * @param dailyUpdateUri a URI that points to a buyer-operated server that hosts updated bidding
 *   data and ads metadata to be used in the on-device ad selection process. The URI must use HTTPS.
 * @param biddingLogicUri the target URI used to fetch bidding logic when a custom audience
 *   participates in the ad selection process. The URI must use HTTPS.
 * @param ads the list of [AdData] objects is a full and complete list of the ads that will be
 *   served by this [CustomAudience] during the ad selection process.
 * @param activationTime optional activation time may be set in the future, in order to serve a
 *   delayed activation. If the field is not set, the object will be activated at the time of
 *   joining.
 * @param expirationTime optional expiration time. Once it has passed, a custom audience is no
 *   longer eligible for daily ad/bidding data updates or participation in the ad selection process.
 *   The custom audience will then be deleted from memory by the next daily update.
 * @param userBiddingSignals optional User bidding signals, provided by buyers to be consumed by
 *   buyer-provided JavaScript during ad selection in an isolated execution environment.
 * @param trustedBiddingSignals optional trusted bidding data, consists of a URI pointing to a
 *   trusted server for buyers' bidding data and a list of keys to query the server with.
 * @param auctionServerRequestFlags the bitfield of auction server request flags. These are flags
 *   that influence the creation of the payload generated by the getAdSelectionData API.
 * @param priority representing the priority of this custom audience. If there is insufficient space
 *   during the buyer input generation in the getAdSelectionData call, the service will attempt to
 *   include the highest priority custom audiences first.
 */
@OptIn(ExperimentalFeatures.Ext14OptIn::class, ExperimentalFeatures.Ext16OptIn::class)
public class CustomAudience
@ExperimentalFeatures.Ext14OptIn
public constructor(
    public val buyer: AdTechIdentifier,
    public val name: String,
    public val dailyUpdateUri: Uri,
    public val biddingLogicUri: Uri,
    public val ads: List<AdData>,
    public val activationTime: Instant? = null,
    public val expirationTime: Instant? = null,
    public val userBiddingSignals: AdSelectionSignals? = null,
    public val trustedBiddingSignals: TrustedBiddingData? = null,
    // Note: public experimental properties are not allowed because the accessors will not appear
    // experimental to Java clients. There are public accessors for these properties below.
    @property:ExperimentalFeatures.Ext14OptIn
    @AuctionServerRequestFlag
    private val auctionServerRequestFlags: Int = 0,
    @property:ExperimentalFeatures.Ext14OptIn private val priority: Double = 0.0,
) {

    @ExperimentalFeatures.Ext16OptIn private var componentAds: List<ComponentAdData> = emptyList()

    /**
     * Creates a custom getter of componentAds property for serving Java client as an experimental
     * public API.
     */
    @ExperimentalFeatures.Ext16OptIn
    public fun getComponentAds(): List<ComponentAdData> {
        return componentAds.toList()
    }

    /**
     * Represents the information necessary for a custom audience to participate in ad selection.
     *
     * A custom audience is an abstract grouping of users with similar demonstrated interests. This
     * class is a collection of some data stored on a device that is necessary to serve
     * advertisements targeting a single custom audience.
     *
     * @param buyer A buyer is identified by a domain in the form "buyerexample.com".
     * @param name The custom audience's name is an arbitrary string provided by the owner and buyer
     *   on creation of the [CustomAudience] object.
     * @param dailyUpdateUri a URI that points to a buyer-operated server that hosts updated bidding
     *   data and ads metadata to be used in the on-device ad selection process. The URI must use
     *   HTTPS.
     * @param biddingLogicUri the target URI used to fetch bidding logic when a custom audience
     *   participates in the ad selection process. The URI must use HTTPS.
     * @param ads the list of [AdData] objects is a full and complete list of the ads that will be
     *   served by this [CustomAudience] during the ad selection process.
     * @param activationTime optional activation time may be set in the future, in order to serve a
     *   delayed activation. If the field is not set, the object will be activated at the time of
     *   joining.
     * @param expirationTime optional expiration time. Once it has passed, a custom audience is no
     *   longer eligible for daily ad/bidding data updates or participation in the ad selection
     *   process. The custom audience will then be deleted from memory by the next daily update.
     * @param userBiddingSignals optional User bidding signals, provided by buyers to be consumed by
     *   buyer-provided JavaScript during ad selection in an isolated execution environment.
     * @param trustedBiddingSignals optional trusted bidding data, consists of a URI pointing to a
     *   trusted server for buyers' bidding data and a list of keys to query the server with.
     */
    public constructor(
        buyer: AdTechIdentifier,
        name: String,
        dailyUpdateUri: Uri,
        biddingLogicUri: Uri,
        ads: List<AdData>,
        activationTime: Instant? = null,
        expirationTime: Instant? = null,
        userBiddingSignals: AdSelectionSignals? = null,
        trustedBiddingSignals: TrustedBiddingData? = null,
    ) : this(
        buyer,
        name,
        dailyUpdateUri,
        biddingLogicUri,
        ads,
        activationTime,
        expirationTime,
        userBiddingSignals,
        trustedBiddingSignals,
        0,
        0.0,
    )

    /**
     * Represents the information necessary for a custom audience to participate in ad selection.
     *
     * A custom audience is an abstract grouping of users with similar demonstrated interests. This
     * class is a collection of some data stored on a device that is necessary to serve
     * advertisements targeting a single custom audience.
     *
     * @param buyer A buyer is identified by a domain in the form "buyerexample.com".
     * @param name The custom audience's name is an arbitrary string provided by the owner and buyer
     *   on creation of the [CustomAudience] object.
     * @param dailyUpdateUri a URI that points to a buyer-operated server that hosts updated bidding
     *   data and ads metadata to be used in the on-device ad selection process. The URI must use
     *   HTTPS.
     * @param biddingLogicUri the target URI used to fetch bidding logic when a custom audience
     *   participates in the ad selection process. The URI must use HTTPS.
     * @param ads the list of [AdData] objects is a full and complete list of the ads that will be
     *   served by this [CustomAudience] during the ad selection process.
     * @param activationTime optional activation time may be set in the future, in order to serve a
     *   delayed activation. If the field is not set, the object will be activated at the time of
     *   joining.
     * @param expirationTime optional expiration time. Once it has passed, a custom audience is no
     *   longer eligible for daily ad/bidding data updates or participation in the ad selection
     *   process. The custom audience will then be deleted from memory by the next daily update.
     * @param userBiddingSignals optional User bidding signals, provided by buyers to be consumed by
     *   buyer-provided JavaScript during ad selection in an isolated execution environment.
     * @param trustedBiddingSignals optional trusted bidding data, consists of a URI pointing to a
     *   trusted server for buyers' bidding data and a list of keys to query the server with.
     * @param auctionServerRequestFlags the bitfield of auction server request flags. These are
     *   flags that influence the creation of the payload generated by the getAdSelectionData API.
     * @param priority representing the priority of this custom audience. If there is insufficient
     *   space during the buyer input generation in the getAdSelectionData call, the service will
     *   attempt to include the highest priority custom audiences first.
     * @param componentAds representing data specific to a component ad that is necessary for ad
     *   selection and rendering. This is to support use case for ads composed of multiple pieces,
     *   such as an ad displaying multiple products at once.
     */
    @ExperimentalFeatures.Ext16OptIn
    public constructor(
        buyer: AdTechIdentifier,
        name: String,
        dailyUpdateUri: Uri,
        biddingLogicUri: Uri,
        ads: List<AdData>,
        activationTime: Instant? = null,
        expirationTime: Instant? = null,
        userBiddingSignals: AdSelectionSignals? = null,
        trustedBiddingSignals: TrustedBiddingData? = null,
        auctionServerRequestFlags: Int = 0,
        priority: Double = 0.0,
        componentAds: List<ComponentAdData> = emptyList(),
    ) : this(
        buyer,
        name,
        dailyUpdateUri,
        biddingLogicUri,
        ads,
        activationTime,
        expirationTime,
        userBiddingSignals,
        trustedBiddingSignals,
        auctionServerRequestFlags,
        priority,
    ) {
        this.componentAds = componentAds.toList()
    }

    /** Checks whether two [CustomAudience] objects contain the same information. */
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is CustomAudience) return false
        return this.buyer == other.buyer &&
            this.name == other.name &&
            this.activationTime == other.activationTime &&
            this.expirationTime == other.expirationTime &&
            this.dailyUpdateUri == other.dailyUpdateUri &&
            this.userBiddingSignals == other.userBiddingSignals &&
            this.trustedBiddingSignals == other.trustedBiddingSignals &&
            this.ads == other.ads &&
            this.componentAds == other.componentAds &&
            this.priority == other.priority &&
            this.auctionServerRequestFlags == other.auctionServerRequestFlags
    }

    /** Returns the hash of the [CustomAudience] object's data. */
    override fun hashCode(): Int {
        var hash = buyer.hashCode()
        hash = 31 * hash + name.hashCode()
        hash = 31 * hash + activationTime.hashCode()
        hash = 31 * hash + expirationTime.hashCode()
        hash = 31 * hash + dailyUpdateUri.hashCode()
        hash = 31 * hash + userBiddingSignals.hashCode()
        hash = 31 * hash + trustedBiddingSignals.hashCode()
        hash = 31 * hash + biddingLogicUri.hashCode()
        hash = 31 * hash + ads.hashCode()
        hash = 31 * hash + componentAds.hashCode()
        hash = 31 * hash + auctionServerRequestFlags.hashCode()
        hash = 31 * hash + priority.hashCode()
        return hash
    }

    override fun toString(): String {
        return "CustomAudience: " +
            "buyer=$buyer, name=$name, activationTime=$activationTime, " +
            "expirationTime=$expirationTime, dailyUpdateUri=$dailyUpdateUri, " +
            "userBiddingSignals=$userBiddingSignals, " +
            "trustedBiddingSignals=$trustedBiddingSignals, " +
            "biddingLogicUri=$biddingLogicUri, ads=$ads, " +
            "auctionServerRequestFlags=$auctionServerRequestFlags, priority=$priority, " +
            "componentAds=$componentAds"
    }

    @ExperimentalFeatures.Ext14OptIn
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @Retention(AnnotationRetention.SOURCE)
    @IntDef(
        flag = true,
        value = [FLAG_AUCTION_SERVER_REQUEST_DEFAULT, FLAG_AUCTION_SERVER_REQUEST_OMIT_ADS],
    )
    public annotation class AuctionServerRequestFlag

    /**
     * Gets the bitfield of auction server request flags. These are flags that influence the
     * creation of the payload generated by the getAdSelectionData API.
     */
    @ExperimentalFeatures.Ext14OptIn
    @AuctionServerRequestFlag
    public fun getAuctionServerRequestFlags(): Int {
        return auctionServerRequestFlags
    }

    /**
     * Gets the priority of this custom audience. If there is insufficient space during the buyer
     * input generation in the getAdSelectionData call, the service will attempt to include the
     * highest priority custom audiences first.
     */
    @ExperimentalFeatures.Ext14OptIn
    public fun getPriority(): Double {
        return priority
    }

    @ExperimentalFeatures.Ext14OptIn
    public companion object {
        /**
         * This auction server request flag indicates to the service that ads for this Custom
         * Audience can be omitted in the server auction payload.
         */
        @ExperimentalFeatures.Ext14OptIn
        public const val FLAG_AUCTION_SERVER_REQUEST_OMIT_ADS: Int =
            android.adservices.customaudience.CustomAudience.FLAG_AUCTION_SERVER_REQUEST_OMIT_ADS

        /** Default value for auction server request flag. */
        @ExperimentalFeatures.Ext14OptIn
        public const val FLAG_AUCTION_SERVER_REQUEST_DEFAULT: Int = 0

        /** Default priority for this custom audience. */
        @ExperimentalFeatures.Ext14OptIn public const val PRIORITY_DEFAULT: Double = 0.0
    }

    /** Builder for [CustomAudience] objects. */
    @SuppressWarnings("OptionalBuilderConstructorArgument")
    public class Builder(
        private var buyer: AdTechIdentifier,
        private var name: String,
        private var dailyUpdateUri: Uri,
        private var biddingLogicUri: Uri,
        private var ads: List<AdData>,
    ) {
        private var activationTime: Instant? = null
        private var expirationTime: Instant? = null
        private var userBiddingSignals: AdSelectionSignals? = null
        private var trustedBiddingData: TrustedBiddingData? = null
        @AuctionServerRequestFlag
        private var auctionServerRequestFlag: Int = FLAG_AUCTION_SERVER_REQUEST_DEFAULT
        private var priority: Double = PRIORITY_DEFAULT
        private var componentAds: List<ComponentAdData> = emptyList()

        /**
         * Sets the buyer [AdTechIdentifier].
         *
         * @param buyer A buyer is identified by a domain in the form "buyerexample.com".
         */
        public fun setBuyer(buyer: AdTechIdentifier): Builder = apply { this.buyer = buyer }

        /**
         * Sets the [CustomAudience] object's name.
         *
         * @param name The custom audience's name is an arbitrary string provided by the owner and
         *   buyer on creation of the [CustomAudience] object.
         */
        public fun setName(name: String): Builder = apply { this.name = name }

        /**
         * On creation of the [CustomAudience] object, an optional activation time may be set in the
         * future, in order to serve a delayed activation. If the field is not set, the
         * [CustomAudience] will be activated at the time of joining.
         *
         * For example, a custom audience for lapsed users may not activate until a threshold of
         * inactivity is reached, at which point the custom audience's ads will participate in the
         * ad selection process, potentially redirecting lapsed users to the original owner
         * application.
         *
         * The maximum delay in activation is 60 days from initial creation.
         *
         * If specified, the activation time must be an earlier instant than the expiration time.
         *
         * @param activationTime activation time, truncated to milliseconds, after which the
         *   [CustomAudience] will serve ads.
         */
        public fun setActivationTime(activationTime: Instant): Builder = apply {
            this.activationTime = activationTime
        }

        /**
         * Once the expiration time has passed, a custom audience is no longer eligible for daily
         * ad/bidding data updates or participation in the ad selection process. The custom audience
         * will then be deleted from memory by the next daily update.
         *
         * If no expiration time is provided on creation of the [CustomAudience], expiry will
         * default to 60 days from activation.
         *
         * The maximum expiry is 60 days from initial activation.
         *
         * @param expirationTime the timestamp [Instant], truncated to milliseconds, after which the
         *   custom audience should be removed.
         */
        public fun setExpirationTime(expirationTime: Instant): Builder = apply {
            this.expirationTime = expirationTime
        }

        /**
         * This URI points to a buyer-operated server that hosts updated bidding data and ads
         * metadata to be used in the on-device ad selection process. The URI must use HTTPS.
         *
         * @param dailyUpdateUri the custom audience's daily update URI
         */
        public fun setDailyUpdateUri(dailyUpdateUri: Uri): Builder = apply {
            this.dailyUpdateUri = dailyUpdateUri
        }

        /**
         * User bidding signals are optionally provided by buyers to be consumed by buyer-provided
         * JavaScript during ad selection in an isolated execution environment.
         *
         * If the user bidding signals are not a valid JSON object that can be consumed by the
         * buyer's JS, the custom audience will not be eligible for ad selection.
         *
         * If not specified, the [CustomAudience] will not participate in ad selection until user
         * bidding signals are provided via the daily update for the custom audience.
         *
         * @param userBiddingSignals an [AdSelectionSignals] object representing the user bidding
         *   signals for the custom audience
         */
        public fun setUserBiddingSignals(userBiddingSignals: AdSelectionSignals): Builder = apply {
            this.userBiddingSignals = userBiddingSignals
        }

        /**
         * Trusted bidding data consists of a URI pointing to a trusted server for buyers' bidding
         * data and a list of keys to query the server with. Note that the keys are arbitrary
         * identifiers that will only be used to query the trusted server for a buyer's bidding
         * logic during ad selection.
         *
         * If not specified, the [CustomAudience] will not participate in ad selection until trusted
         * bidding data are provided via the daily update for the custom audience.
         *
         * @param trustedBiddingSignals a [TrustedBiddingData] object containing the custom
         *   audience's trusted bidding data.
         */
        @SuppressWarnings("MissingGetterMatchingBuilder")
        public fun setTrustedBiddingData(trustedBiddingSignals: TrustedBiddingData): Builder =
            apply {
                this.trustedBiddingData = trustedBiddingSignals
            }

        /**
         * Returns the target URI used to fetch bidding logic when a custom audience participates in
         * the ad selection process. The URI must use HTTPS.
         *
         * @param biddingLogicUri the URI for fetching buyer bidding logic
         */
        public fun setBiddingLogicUri(biddingLogicUri: Uri): Builder = apply {
            this.biddingLogicUri = biddingLogicUri
        }

        /**
         * This list of [AdData] objects is a full and complete list of the ads that will be served
         * by this [CustomAudience] during the ad selection process.
         *
         * If not specified, or if an empty list is provided, the [CustomAudience] will not
         * participate in ad selection until a valid list of ads are provided via the daily update
         * for the custom audience.
         *
         * @param ads a [List] of [AdData] objects representing ads currently served by the custom
         *   audience.
         */
        public fun setAds(ads: List<AdData>): Builder = apply { this.ads = ads }

        /**
         * Sets the priority for this CustomAudience with respect to other CustomAudiences for this
         * buyer.
         *
         * <p>This means if there is insufficient space during buyer input generation in the {@link
         * android.adservices.adselection.AdSelectionManager#getAdSelectionData(GetAdSelectionDataRequest,
         * Executor, OutcomeReceiver)} call, the service will attempt to include the highest
         * priority custom audiences first.
         *
         * <p>The default value if this field is not set is 0.0.
         *
         * @param priority a [Double] representing the priority of this custom audience.
         */
        @OptIn(ExperimentalFeatures.Ext14OptIn::class)
        @ExperimentalFeatures.Ext14OptIn
        public fun setPriority(priority: Double): Builder = apply { this.priority = priority }

        /**
         * Returns the bitfield of auction server request flags. These are flags that influence the
         * creation of the payload generated by the {@link
         * android.adservices.adselection.AdSelectionManager#getAdSelectionData(GetAdSelectionDataRequest,
         * Executor, OutcomeReceiver)} API.
         *
         * <p>To create this bitfield, place an {@code |} bitwise operator between each {@link
         * AuctionServerRequestFlag} to be enabled.
         *
         * @param auctionServerRequestFlag a [Integer] representing the auction server request flag.
         */
        @OptIn(ExperimentalFeatures.Ext14OptIn::class)
        @ExperimentalFeatures.Ext14OptIn
        public fun setAuctionServerRequestFlags(
            @AuctionServerRequestFlag auctionServerRequestFlag: Int
        ): Builder = apply { this.auctionServerRequestFlag = auctionServerRequestFlag }

        /**
         * Sets the components ads served by the custom audience.
         *
         * @param componentAds a [List] of [ComponentAdData] objects representing component ads
         *   currently served by the custom audience.
         */
        @OptIn(ExperimentalFeatures.Ext16OptIn::class)
        @ExperimentalFeatures.Ext16OptIn
        public fun setComponentAds(componentAds: List<ComponentAdData>): Builder = apply {
            this.componentAds = componentAds
        }

        /** Builds an instance of a [CustomAudience]. */
        public fun build(): CustomAudience {
            return CustomAudience(
                buyer,
                name,
                dailyUpdateUri,
                biddingLogicUri,
                ads,
                activationTime,
                expirationTime,
                userBiddingSignals,
                trustedBiddingData,
                auctionServerRequestFlag,
                priority,
                componentAds,
            )
        }
    }
}
